Skip to content

fix(assertions): add Task reference forwarders on AsyncDelegateAssertion#5618

Merged
thomhurst merged 1 commit intomainfrom
fix/5613-asyncdelegateassertion-task-forwarders
Apr 18, 2026
Merged

fix(assertions): add Task reference forwarders on AsyncDelegateAssertion#5618
thomhurst merged 1 commit intomainfrom
fix/5613-asyncdelegateassertion-task-forwarders

Conversation

@thomhurst
Copy link
Copy Markdown
Owner

Summary

Addresses findings #1 and #3 of issue #5613.

Assert.That(Task task) returns AsyncDelegateAssertion, which implements both IAssertionSource<object?> and IAssertionSource<Task>. The ambiguity breaks C# generic inference on a handful of otherwise-natural assertions:

  • Assert.That(task).IsNotNull() / IsNull()CS1929 with a misleading diagnostic pointing at JsonElementAssertionExtensions.IsNotNull(IAssertionSource<JsonElement>) (compiler's fallback "best candidate").
  • Assert.That(task).IsSameReferenceAs(other) / IsNotSameReferenceAs(other)CS0411 ("type arguments cannot be inferred").

Fix: add four instance methods on AsyncDelegateAssertion that forward to the IAssertionSource<Task> interface. This matches the pattern already used on the same class for IsCompleted / IsFaulted / IsCanceled / IsTypeOf / etc., which exist for the same reason.

No behavior change for existing callers — the forwarders resolve an inference ambiguity that previously required ((object)task) or <Task> annotations.

Remaining findings (#2, #4, #5) will be addressed in follow-up PRs.

Test plan

  • New regression tests in TUnit.Assertions.Tests/Bugs/Issue5613Tests.cs (4 tests, one per method). Confirmed failing-to-compile before the fix, passing after.
  • Full TUnit.Assertions.Tests suite passes on net10 (1978), net9 (1978), net8 (1926), net472 (1778).
  • TUnit.PublicAPI snapshots updated for all four TFMs (only change: the 4 new public methods on AsyncDelegateAssertion).

…ion (#5613)

AsyncDelegateAssertion implements both IAssertionSource<object?> and
IAssertionSource<Task>, breaking C# generic inference for IsNotNull,
IsNull, IsSameReferenceAs, and IsNotSameReferenceAs on Assert.That(Task).

- IsNotNull / IsNull produced a misleading CS1929 pointing at JsonElement
- IsSameReferenceAs / IsNotSameReferenceAs produced CS0411

Add instance methods that forward to the IAssertionSource<Task> interface,
matching the existing pattern used for IsCompleted / IsFaulted / IsCanceled.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a well-scoped, targeted fix for the C# inference ambiguity caused by AsyncDelegateAssertion implementing both IAssertionSource<object?> and IAssertionSource<Task>.

What's Good

  • Correct pattern: The four new methods exactly mirror the existing forwarding pattern used for IsCompleted, IsFaulted, IsCanceled, IsTypeOf, etc. — no new approaches, no surprises.
  • Proper regression tests: All four methods get dedicated tests in Issue5613Tests.cs that confirm they compile and pass, which is the exact invariant being protected.
  • Public API snapshots updated: All four TFMs covered. Good hygiene.
  • Minimal scope: No unrelated refactoring or cleanup included.

Architectural Observation (not a blocker)

The class now has roughly 20+ instance-method forwarders to resolve the dual-interface ambiguity, and this PR adds 4 more. The concern is that this is an open-ended maintenance tax: every future generic assertion added to IAssertionSource<T> (e.g. a hypothetical IsEquivalentTo, HasSameHashCodeAs, etc.) may silently re-introduce the same CS0411/CS1929 problem on AsyncDelegateAssertion unless a forwarder is also added.

A more sustainable approach to consider for a follow-up (not this PR) would be to expose the IAssertionSource<Task> view as a distinct fluent entry point — e.g. Assert.That(task).AsTask() returning IAssertionSource<Task> — so the dual-interface is only needed internally, not as a public API surface. That eliminates the ambiguity entirely without requiring per-method forwarders. Given findings #2, #4, #5 are still outstanding, it might be worth tracking this as part of the broader #5613 clean-up.

Minor Note

The XML doc on IsNotNull() says "Without this instance method, Assert.That(task).IsNotNull() would fail generic inference..." — this is exactly the right kind of WHY comment and is appreciated here.

Verdict: Approve. The fix is correct, minimal, well-tested, and consistent with the existing class patterns.

@thomhurst thomhurst merged commit cbd836b into main Apr 18, 2026
14 checks passed
@thomhurst thomhurst deleted the fix/5613-asyncdelegateassertion-task-forwarders branch April 18, 2026 11:40
This was referenced Apr 18, 2026
intellitect-bot pushed a commit to IntelliTect/EssentialCSharp.Web that referenced this pull request Apr 21, 2026
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.35.2 to
1.37.10.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.37.10

<!-- Release notes generated using configuration in .github/release.yml
at v1.37.10 -->

## What's Changed
### Other Changes
* docs(test-filters): add migration callout for --filter →
--treenode-filter by @​johnkattenhorn in
thomhurst/TUnit#5628
* fix: re-enable RPC tests and modernize harness (#​5540) by @​thomhurst
in thomhurst/TUnit#5632
* fix(mocks): propagate [Obsolete] and null-forgiving raise dispatch
(#​5626) by @​JohnVerheij in
thomhurst/TUnit#5631
* ci: use setup-dotnet built-in NuGet cache by @​thomhurst in
thomhurst/TUnit#5635
* feat(playwright): propagate W3C trace context into browser contexts by
@​thomhurst in thomhurst/TUnit#5636
### Dependencies
* chore(deps): update tunit to 1.37.0 by @​thomhurst in
thomhurst/TUnit#5625

## New Contributors
* @​johnkattenhorn made their first contribution in
thomhurst/TUnit#5628
* @​JohnVerheij made their first contribution in
thomhurst/TUnit#5631

**Full Changelog**:
thomhurst/TUnit@v1.37.0...v1.37.10

## 1.37.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.37.0 -->

## What's Changed
### Other Changes
* fix: stabilize flaky tests across analyzer, OTel, and engine suites by
@​thomhurst in thomhurst/TUnit#5609
* perf: engine hot-path allocation wins (#​5528 B) by @​thomhurst in
thomhurst/TUnit#5610
* feat(analyzers): detect collection IsEqualTo reference equality
(TUnitAssertions0016) by @​thomhurst in
thomhurst/TUnit#5615
* perf: consolidate test dedup + hook register guards (#​5528 A) by
@​thomhurst in thomhurst/TUnit#5612
* perf: engine discovery/init path cleanup (#​5528 C) by @​thomhurst in
thomhurst/TUnit#5611
* fix(assertions): render collection contents in IsEqualTo failure
messages (#​5613 B) by @​thomhurst in
thomhurst/TUnit#5619
* feat(analyzers): code-fix for TUnit0015 to insert CancellationToken
(#​5613 D) by @​thomhurst in
thomhurst/TUnit#5621
* fix(assertions): add Task reference forwarders on
AsyncDelegateAssertion by @​thomhurst in
thomhurst/TUnit#5618
* test(asp-net): fix race in FactoryMethodOrderTests by @​thomhurst in
thomhurst/TUnit#5623
* feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource]
(#​5613 C) by @​thomhurst in
thomhurst/TUnit#5620
* fix(pipeline): isolate AOT publish outputs to stop clobbering pack
DLLs (#​5622) by @​thomhurst in
thomhurst/TUnit#5624
### Dependencies
* chore(deps): update tunit to 1.36.0 by @​thomhurst in
thomhurst/TUnit#5608
* chore(deps): update modularpipelines to 3.2.8 by @​thomhurst in
thomhurst/TUnit#5614


**Full Changelog**:
thomhurst/TUnit@v1.36.0...v1.37.0

## 1.36.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.36.0 -->

## What's Changed
### Other Changes
* fix: don't render test's own trace as Linked Trace in HTML report by
@​thomhurst in thomhurst/TUnit#5580
* fix(docs): benchmark index links 404 by @​thomhurst in
thomhurst/TUnit#5587
* docs: replace repeated benchmark link suffix with per-test
descriptions by @​thomhurst in
thomhurst/TUnit#5588
* docs: clearer distributed tracing setup and troubleshooting by
@​thomhurst in thomhurst/TUnit#5597
* fix: auto-suppress ExecutionContext flow for hosted services (#​5589)
by @​thomhurst in thomhurst/TUnit#5598
* feat: auto-align DistributedContextPropagator to W3C by @​thomhurst in
thomhurst/TUnit#5599
* feat: TUnit0064 analyzer + code fix for direct WebApplicationFactory
inheritance by @​thomhurst in
thomhurst/TUnit#5601
* feat: auto-propagate test trace context through IHttpClientFactory by
@​thomhurst in thomhurst/TUnit#5603
* feat: TUnit.OpenTelemetry zero-config tracing package by @​thomhurst
in thomhurst/TUnit#5602
* fix: restore [Obsolete] members removed in v1.27 (#​5539) by
@​thomhurst in thomhurst/TUnit#5605
* feat: generalize OTLP receiver for use outside TUnit.Aspire by
@​thomhurst in thomhurst/TUnit#5606
* feat: auto-configure OpenTelemetry in TestWebApplicationFactory SUT by
@​thomhurst in thomhurst/TUnit#5607
### Dependencies
* chore(deps): update tunit to 1.35.2 by @​thomhurst in
thomhurst/TUnit#5581
* chore(deps): update dependency typescript to ~6.0.3 by @​thomhurst in
thomhurst/TUnit#5582
* chore(deps): update dependency coverlet.collector to v10 by
@​thomhurst in thomhurst/TUnit#5600


**Full Changelog**:
thomhurst/TUnit@v1.35.2...v1.36.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.35.2...v1.37.10).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit&package-manager=nuget&previous-version=1.35.2&new-version=1.37.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant